/**
 * 
 * Wuyts Sam	
 * 2Ba Ingenieurwetenschappen 
 * Hoofdrichting Werktuigkunde
 * Nevenrichting Computerwetenschappen 
 * https://ogp-sam-michiel.googlecode.com/svn:Asteroids
 * 
 * Partner: Michiel Timmermans
 */

package asteroids;

import be.kuleuven.cs.som.annotate.*;
import java.util.*;
import asteroids.model.programs.*;

public class Ship extends FlyingObject {
	private Angle ang;
	public final static double minSize = 10;
	private Thruster thruster;
	private List<Bullet> firedBullets;
	public final static double maxBullets = 3;
	private boolean hasProgram;
	private Program program;

	/**
	 * Creates a new ship with the given position, velocity, size and angle.
	 * 
	 * @param position
	 *            The starting position of the ship to be created
	 * @param velocity
	 *            The starting velocity of the ship to be created
	 * @param angle
	 *            The direction the new ship faces, expressed in radians
	 * @param size
	 *            The size of the ship to be created.
	 * @invar The size of the ship should at all time be valid and equal to the
	 *        given size. |this.isValidSize(this.getSize()) && this.getSize ==
	 *        size
	 * @invar The mass of the ship should at all times be valid and equal to the
	 *        given mass. | this.getMass > 0 && this.getMass == mass
	 * @pre This new ship can have the given angle as it's angle. | angle !=
	 *      null
	 * @post the new position of this new ship is equal to the given initial
	 *       position. |(new this).getPosition() == position
	 * @post the new velocity of this new ship is equal to the given initial
	 *       velocity. If the given velocity is a null element, we use a new
	 *       velocity that is zero in both directions as the provided velocity.
	 *       |if( velocity != null) (new this).getVelocity() == velocity | else
	 *       (new this).getVelocity() == new Velocity(0,0)
	 * @post the new direction of this new ship is equal to the given initial
	 *       direction. |(new this).getAngle() == angle
	 * @post the size of this new ship is equal to the given initial size. |
	 *       (new this).getSize() == size
	 * 
	 * @throws NullPointerException
	 *             This new ship cannot have the given position as its position.
	 *             |position == null
	 * @throws IllegalArgumentException
	 *             This new ship cannot have the given size as its size. |
	 *             !isValidSize(size)
	 */
	public Ship(Position position, Velocity velocity, Angle angle, double size,
			double mass) {
		super(position, velocity, size, mass);
		assert !(angle == null);
		this.ang = angle;
		this.thruster = new Thruster();
		firedBullets = new ArrayList<Bullet>();
		hasProgram = false;
	}

	public void loadProgram(Program newProgram) {
		this.program = newProgram;
		this.hasProgram = true;
		this.program.loadOnToShip(this);
	}
	
	public void executeProgram() {
		if (this.hasProgram){
			this.program.execute();
		}
	}

	/**
	 * 
	 * @return A list containing this ship's thrusters | result ==
	 *         this.firedBullets
	 */
	@Basic
	@Immutable
	public List<Bullet> getFiredBullets() {
		return this.firedBullets;
	}

	/**
	 * @post This ship's list of fired bullets contains the provided bullet |
	 *       (new this).getFiredBullets().contains(bullet) == true
	 * @effect The provided bullet is added to the end of this ships list of
	 *         fired bullets | this.getFiredBullets().add(bullet)
	 * @throws IllegalArgumentException
	 *             | this ship can't have the provided Bullet as a fired bullet
	 *             | !this.canHaveAsBullet(bullet)
	 * @throws IllegalStateException
	 *             | this.hasMaximumBullets()
	 * 
	 */
	public void addFiredBullet(Bullet bullet) throws IllegalArgumentException,
			IllegalStateException {
		if (!this.canHaveAsBullet(bullet))
			throw new IllegalArgumentException();
		if (this.hasMaximumBullets())
			throw new IllegalStateException();
		this.getFiredBullets().add(bullet);
	}

	/**
	 * 
	 * @param bullet
	 *            The bullet to be removed
	 * @throws IllegalArgumentException
	 *             bullet is a null object, or this ship's list of fired Bullets
	 *             does not contain bullet | bullet == null ||
	 *             !this.hasFired(bullet)
	 */
	public void removeFiredBullet(Bullet bullet)
			throws IllegalArgumentException {
		if (bullet == null || !this.hasFired(bullet))
			throw new IllegalArgumentException();
		this.getFiredBullets().remove(bullet);
	}

	/**
	 * 
	 * @param bullet
	 *            The bullet to be tested
	 * @return true if this ships list of fired bullets contains bullet | result
	 *         == this.getFiredBullets().contains(bullet)
	 * @throws IllegalArgumentException
	 *             the provided bullet is a null object | bullet == null
	 */
	public boolean hasFired(Bullet bullet) throws IllegalArgumentException {
		if (bullet == null)
			throw new IllegalArgumentException();
		return this.getFiredBullets().contains(bullet);
	}

	/**
	 * 
	 * @return True if adding another bullet to the list of fired bullets would
	 *         cause the size of the list to exceed maxBullets | result ==
	 *         (this.getFiredBullets().size() >= maxBullets)
	 */
	public boolean hasMaximumBullets() {
		return this.getFiredBullets().size() >= maxBullets;
	}

	/**
	 * 
	 * @param bullet
	 *            the bullet which wants to be fired
	 * @return False if the bullet is a null-object, or the list of fired
	 *         bullets already contains this bullet, or the provided bullet
	 *         belongs to another ship true in other cases. |result == !(bullet
	 *         == null) && !this.gethasFired(bullet) && bullet.getSource()==this
	 */
	public boolean canHaveAsBullet(Bullet bullet) {
		if (bullet == null || this.hasFired(bullet)
				|| bullet.getSource() != this)
			return false;
		return true;
	}

	/**
	 * 
	 * @return this ship's thruster | result == this.thruster
	 */
	@Basic
	@Immutable
	public Thruster getThruster() {
		return this.thruster;
	}

	/**
	 * 
	 * @return Returns the current angle of the ship.
	 */
	@Basic
	public Angle getAngle() {
		return this.ang;
	}

	/**
	 * Sets the angle of the ship to the one specified.
	 * 
	 * @param angle
	 * @pre the provided angle must not be null | angle != null
	 * @post The new angle equals the provided angle | (new this).getAngle() ==
	 *       angle
	 */
	@Basic
	public void setAngle(Angle angle) {
		assert !(angle == null);
		this.ang = angle;
	}

	/**
	 * 
	 * @return The current status of the ship's thruster.
	 */
	@Basic
	public boolean getThrusterStatus() {
		return this.thruster.getThrusterStatus();
	}

	/**
	 * Sets the status of the ship's thruster to the given status.
	 * 
	 * @param newStatus
	 *            the new thruster status.
	 */
	@Basic
	public void setThrusterStatus(boolean newStatus) {
		this.thruster.setThrusterStatus(newStatus);
	}

	/**
	 * 
	 * @return returns the output force of this ship's thruster |result ==
	 *         this.getThruster().getOutputForce();
	 */
	@Immutable
	public double getThrusterOutput() {
		return this.thruster.getOutputForce();
	}

	/**
	 * Moves the FlyingObject based on the current position and velocity, and
	 * the given time deltaT.
	 * 
	 * @param deltaT
	 *            The amount of time over which the ship must be moved.
	 * @post The position is changed according to the given time, if valid, and
	 *       current angle and speed. |(new this).getPosition().getX ==
	 *       (this.getPosition().getX + deltaT*this.getVelocity.getX) |(new
	 *       this).getPosition().getY() == (this.getPosition().getY() +
	 *       deltaT*this.getVelocity.getY)
	 * @post If the thruster is enabled, the velocity is updated, based on the
	 *       current speed, the mass, the current angle and the force of the
	 *       thruster according to Newton's law | if(getThrusterStatus) |(new
	 *       this).getVelocity().getX() == this.getVelocity().getX() +
	 *       this.getThrusterOutput()/this.getMass()*
	 *       Math.cos(this.getAngle().getAngle()) | new
	 *       this).getVelocity().getY() == this.getVelocity().getY() +
	 *       this.getThrusterOutput()/this.getMass()*
	 *       Math.sin(this.getAngle().getAngle())
	 * 
	 * @throws IllegalArgumentException
	 *             The given amount of time is either not a number or is smaller
	 *             than zero. |(deltaT < 0) || (Double.isNaN(deltaT))
	 */
	@Override
	public void move(double deltaT) throws IllegalArgumentException {
		Position newPos = Vector.move(deltaT, this.getPosition(),
				this.getVelocity());
		this.setPosition(newPos);
		if (this.getThrusterStatus()) {
			double totalAcceleration = this.getThrusterOutput()
					/ this.getMass();
			double xAcceleration = totalAcceleration
					* Math.cos(this.getAngle().getAngle());
			double yAcceleration = totalAcceleration
					* Math.sin(this.getAngle().getAngle());
			double newXVel = this.getVelocity().getX()
					+ (xAcceleration * deltaT);
			double newYVel = this.getVelocity().getY()
					+ (yAcceleration * deltaT);
			Velocity newVel = new Velocity(newXVel, newYVel);
			this.setVelocity(newVel);
		}
	}

	/**
	 * Checks of the given angle can be added.
	 * 
	 * @param alpha
	 *            The angle to be checked.
	 * @return True if and only if the given angle can be added. | result ==
	 *         Angle.isValidAngle(alpha+this.getAngle().getAngle())
	 */
	public boolean angleCanBeAdded(double alpha) {
		return Angle.isValidAngle(alpha + this.getAngle().getAngle());
	}

	/**
	 * Turns the ship with the given angle.
	 * 
	 * @param alpha
	 *            The angle to be added to the current one.
	 * @pre The given angle must be valid. | angleCanBeAdded(alpha) == true
	 * @post The new angle is equal to the sum of the current and the given one.
	 *       | (new this).getAngle() == new Angle(alpha +
	 *       this.getAngle().getAngle())
	 */
	public void turn(double alpha) {
		assert angleCanBeAdded(alpha);
		this.ang = new Angle(alpha + this.getAngle().getAngle());
	}

	/**
	 * Changes The velocity of the ship by the given amount
	 * 
	 * @param a
	 *            The amount of velocity.
	 * @post The velocity is increased by the given amount. If the given amount
	 *       is not valid, the velocity remains the same.
	 */
	public void thrust(double a) {

		if (a < 0 || Double.isNaN(a)) {
			a = 0;
		}

		double vx = this.getVelocity().getX() + a
				* Math.cos(this.getAngle().getAngle());
		double vy = this.getVelocity().getY() + a
				* Math.sin(this.getAngle().getAngle());
		this.speed = new Velocity(vx, vy);

	}

	/**
	 * 
	 * @param thing
	 * @return | result == ( (thing == this)||( getDistanceBetween(this, thing)
	 *         |< 0) )&& !(thing instanceof Bullet && ((Bullet)
	 *         |thing).getSource() == this)
	 * @throws NullPointerException
	 *             | thing == null
	 */
	@Override
	public boolean overlap(FlyingObject thing) throws NullPointerException {
		if (thing == null) {
			throw new NullPointerException();
		} else if (thing instanceof Bullet
				&& ((Bullet) thing).getSource() == this) {
			return false;
		}

		else if (thing == this) {
			return true;
		} else {
			return (this.getDistanceBetween(thing) < 0);
		}

	}

}